#ifndef SIMPLE_SUPPORT_ITERATOR_OFFSET_EXPANDER_HPP
#define SIMPLE_SUPPORT_ITERATOR_OFFSET_EXPANDER_HPP

#include <type_traits> // std::remove_reference_t std::enable_if_t std::is_same_v std::is_reference_v
#include <iterator> // std::iterator_traits std::begin
#include <utility> // std::forward std::move
#include <cassert> // assert

namespace simple::support
{

	// an offset based iterator to which you can assign one past the end to emplace_back
	template <typename Array>
	struct offset_expander
	{
		using offset_type = typename std::remove_reference_t<Array>::size_type; // container type traits? are those existed??
		using iterator_type = typename std::remove_reference_t<Array>::iterator;

		using value_type = typename std::iterator_traits<iterator_type>::value_type;
		using difference_type = typename std::iterator_traits<iterator_type>::difference_type;
		using pointer = typename std::iterator_traits<iterator_type>::pointer;
		using reference = typename std::iterator_traits<iterator_type>::reference;
		using iterator_category = typename std::iterator_traits<iterator_type>::iterator_category;
		Array array;
		offset_type offset;

		template <typename A, std::enable_if_t<std::is_same_v<A,Array>>* = nullptr>
		explicit offset_expander(A&& array, offset_type offset = 0)
			: array(std::forward<A>(array)), offset(offset)
		{}

		template <typename A, std::enable_if_t<std::is_same_v<A,Array>>* = nullptr>
		explicit offset_expander(A&& array, iterator_type i)
			: array(std::forward<A>(array)), offset(static_cast<offset_type>(i - array.begin()))
		{}

		offset_expander(const offset_expander&) = default;
		offset_expander(offset_expander&&) = default;
		// TODO: in c++20 replace with:
		// constexpr
		// offset_expander& operator=(const offset_expander& other)
		// 	noexcept(noexcept(std::declval<offset_type> = other.offset))
		// 	reuires reference<Array>
		// {
		// 	assert(&array == &other.array);
		// 	offset = other.offset;
		// 	return *this;
		// }
		// offset_expander& operator=(const offset_expander&) = default;
		// offset_expander& operator=(offset_expander&&) = default;
		offset_expander& operator=(const offset_expander& other) &
		{
			if constexpr (not std::is_reference_v<Array>)
				array = other.array;
			else
				assert(&array == &other.array);
			offset = other.offset;
			return *this;
		}
		offset_expander& operator=(const offset_expander& other) && = delete;
		offset_expander& operator=(offset_expander&& other) &
		{
			if constexpr (not std::is_reference_v<Array>)
				array = std::move(other.array);
			else
				assert(&array == &other.array);
			offset = other.offset;
			return *this;
		}
		offset_expander& operator=(offset_expander&& other) && = delete;

		friend bool operator==(const offset_expander& one, const offset_expander& other)
		{
			// tempted to, but that would be premature optimization and limit usefulness
			// assert(one.offset != other.offset || one.base() == other.base());
			// return one.offset == other.offset;
			return one.base() == other.base();
		}
		friend bool operator!=(const offset_expander& one, const offset_expander& other)
		{
			return !(one == other);
		}

		offset_expander& operator++() { ++offset; return *this; }
		offset_expander operator++(int) { offset_expander prev = *this; ++(*this); return prev; }
		offset_expander& operator--() { --offset; return *this; }
		offset_expander operator--(int) { offset_expander prev = *this; --(*this); return prev; }

		friend offset_expander operator+(offset_expander i, difference_type d) { i.offset = i.offset + d; return i; }
		friend offset_expander operator+(difference_type d, offset_expander i) { return i + d; }
		friend difference_type operator-(offset_expander from, offset_expander x) { assert(&from.array == &x.array); return from.offset - x.offset; }

		// TODO: relational operators

		class proxy
		{
			Array& array;
			offset_type offset;

			proxy(Array& array, offset_type offset) : array(array), offset(offset) {}
			friend struct offset_expander;
			public:
			proxy() = delete;
			proxy(const proxy&) = delete;
			proxy(const proxy&&) = delete;
			proxy& operator=(const proxy&) = delete;
			proxy& operator=(const proxy&&) = delete;

			template <typename T>
			const proxy& operator=(T&& x) const
			{
				assert(offset <= array.size() && offset >= 0);
				if(offset != array.size())
					array[offset] = std::forward<T>(x);
				else
					array.emplace_back(std::forward<T>(x));
				return *this;
			}

			operator reference() const
			{
				assert(offset < array.size() && offset >= 0);
				return array[offset];
			}

		};

		const proxy operator*() const
		{
			return {array, offset};
		}

		auto base() const
		{
			assert(offset <= array.size()); // ok to form iterator one past end
			assert(offset >= 0);
			using std::begin;
			return begin(array) + offset;
		}

	};
	template <typename A> offset_expander(A&&, typename offset_expander<A>::offset_type) -> offset_expander<A>;
	template <typename A> offset_expander(A&&, typename offset_expander<A>::iterator_type) -> offset_expander<A>;
	template <typename A> offset_expander(A&&) -> offset_expander<A>;


} // namespace simple::support

#endif /* end of include guard */
